// Copyright (c) 2024 Huawei Device Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Based on shader_disk_cache.cc originally written by
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "arkweb/chromium_ext/services/network/prp_preload/src/disk_cache_file.h"

#include "base/logging.h"
#include "base/task/single_thread_task_runner.h"

namespace {
  constexpr int32_t WAIT_INITED_TIME_OUT = 200; // 200ms
}  // namespace

namespace ohos_prp_preload {
DiskCacheEntry::DiskCacheEntry(DiskCacheFile* cache,
                               const std::string& url,
                               const std::string& entry_content) :
  cache_(cache), url_(raw_ref(url)), entry_content_(entry_content), entry_(nullptr) {
  weak_ptr_ = weak_ptr_factory_.GetWeakPtr();
}

DiskCacheEntry::~DiskCacheEntry() {
  if (entry_) {
    entry_->Close();
    entry_ = nullptr;
  }
}

void DiskCacheEntry::Cache() {
  OnOpComplete(net::OK);
}

void DiskCacheEntry::OnOpComplete(int rv) {
  auto weak_ptr = std::move(weak_ptr_);
  do {
    switch (op_type_) {
      case OPEN_ENTRY:
        rv = OpenCallback(rv);
        break;
      case CREATE_ENTRY:
        rv = WriteCallback(rv);
        break;
      case WRITE_DATA:
        rv = IOComplete(rv);
        break;
    }
  } while (rv != net::ERR_IO_PENDING && weak_ptr);
  if (weak_ptr) {
    weak_ptr_ = std::move(weak_ptr);
  }
}

void DiskCacheEntry::OnEntryOpenComplete(disk_cache::EntryResult result) {
  int rv = result.net_error();
  entry_ = result.ReleaseEntry();
  OnOpComplete(rv);
}

int DiskCacheEntry::OpenCallback(int rv) {
  op_type_ = CREATE_ENTRY;

  auto callback = base::BindOnce(&DiskCacheEntry::OnEntryOpenComplete,
                                 weak_ptr_factory_.GetWeakPtr());

  if (cache_ == nullptr) {
    return rv;
  }
  disk_cache::Backend* backend = cache_->Backend();
  if (backend == nullptr) {
    return net::ERR_FAILED;
  }
  disk_cache::EntryResult create_result =
      backend->OpenOrCreateEntry(*url_, net::HIGHEST, std::move(callback));
  rv = create_result.net_error();
  if (rv != net::ERR_IO_PENDING) {
    entry_ = create_result.ReleaseEntry();
  }
  return rv;
}

int DiskCacheEntry::WriteCallback(int rv) {
  if (rv != net::OK) {
    if (cache_ != nullptr) {
      cache_->EntryWriteComplete(this);
    }
    return rv;
  }

  op_type_ = WRITE_DATA;
  auto io_buf = base::MakeRefCounted<net::StringIOBuffer>(entry_content_);
  if (!entry_) {
    return net::ERR_FAILED;
  }
  return entry_->WriteData(1, 0, io_buf.get(), entry_content_.length(),
                           base::BindOnce(&DiskCacheEntry::OnOpComplete,
                                          weak_ptr_factory_.GetWeakPtr()), true);
}

int DiskCacheEntry::IOComplete(int rv) {
  if (cache_ != nullptr) {
    cache_->EntryWriteComplete(this);
  }
  return rv;
}


DiskCacheReadHelper::DiskCacheReadHelper(DiskCacheFile* cache,
                                         const std::string& url,
                                         const EntryLoadedCallback& callback) :
  cache_(cache), url_(raw_ref(url)), entry_loaded_cb_(callback), buf_(nullptr), entry_(nullptr) {
  weak_ptr_ = weak_ptr_factory_.GetWeakPtr();
}

DiskCacheReadHelper::~DiskCacheReadHelper() {
  if (entry_) {
    entry_->Close();
    entry_ = nullptr;
  }
  if (buf_) {
    buf_ = nullptr;
  }
}

void DiskCacheReadHelper::LoadCache() {
  OnOpComplete(net::OK);
}

void DiskCacheReadHelper::OnOpComplete(int rv) {
  auto weak_ptr = std::move(weak_ptr_);
  do {
    switch (op_type_) {
      case OPEN_ENTRY:
        rv = OpenCallback(rv);
        break;
      case LOAD_ENTRY:
        rv = ReadCallback(rv);
        break;
      case READ_DATA:
        rv = IOComplete(rv);
        break;
    }
  } while (rv != net::ERR_IO_PENDING && weak_ptr);
  if (weak_ptr) {
    weak_ptr_ = std::move(weak_ptr);
  }
}

void DiskCacheReadHelper::OnEntryOpenComplete(disk_cache::EntryResult result) {
  int rv = result.net_error();
  entry_ = result.ReleaseEntry();
  OnOpComplete(rv);
}

int DiskCacheReadHelper::OpenCallback(int rv) {
  op_type_ = LOAD_ENTRY;

  auto callback = base::BindOnce(&DiskCacheReadHelper::OnEntryOpenComplete,
                                 weak_ptr_factory_.GetWeakPtr());

  if (cache_ == nullptr) {
    return rv;
  }
  disk_cache::Backend* backend = cache_->Backend();
  if (backend == nullptr) {
    return net::ERR_FAILED;
  }
  disk_cache::EntryResult result =
      backend->OpenEntry(*url_, net::HIGHEST, std::move(callback));
  rv = result.net_error();

  if (rv != net::ERR_IO_PENDING)
    entry_ = result.ReleaseEntry();  // may be nullptr
  return rv;
}

int DiskCacheReadHelper::ReadCallback(int rv) {
  if (rv != net::OK) {
    LOG(DEBUG) << "PRPPreload.DiskCacheReadHelper::ReadCallback load cache entry failed: " << rv;
    if (!entry_loaded_cb_.is_null()) {
      entry_loaded_cb_.Run(std::string());
    }
    if (cache_ != nullptr) {
      cache_->EntryReadComplete();
    }
    return rv;
  }

  op_type_ = READ_DATA;
  buf_ = base::MakeRefCounted<net::IOBufferWithSize>(entry_->GetDataSize(1));
  if (!entry_ || !buf_) {
    return net::ERR_FAILED;
  }
  return entry_->ReadData(1, 0, buf_.get(), buf_->size(),
                          base::BindOnce(&DiskCacheReadHelper::OnOpComplete,
                                         weak_ptr_factory_.GetWeakPtr()));
}

int DiskCacheReadHelper::IOComplete(int rv) {
  if (rv && buf_ != nullptr && rv == buf_->size()) {
    if (!entry_loaded_cb_.is_null()) {
      entry_loaded_cb_.Run(std::string(buf_->data(), buf_->size()));
    }
  } else {
    if (!entry_loaded_cb_.is_null()) {
      entry_loaded_cb_.Run(std::string());
    }
  }

  if (cache_ != nullptr) {
    cache_->EntryReadComplete();
  }
  return rv;
}

DiskCacheFile::DiskCacheFile(const scoped_refptr<DiskCacheBackendFactory>& disk_cache_backend_factory,
                             const std::string& url,
                             const EntryLoadedCallback& entry_loaded_cb) :
  disk_cache_backend_factory_(disk_cache_backend_factory), url_(raw_ref(url)), entry_loaded_cb_(entry_loaded_cb) {}

void DiskCacheFile::StoreInfoAsync(const std::string& entry_content) {
  if (!disk_cache_backend_factory_) {
    return;
  }
  if (!disk_cache_backend_factory_->CheckBackendAsync(
      base::BindOnce(&DiskCacheFile::BackendComplete, weak_factory_.GetWeakPtr()))) {
    LOG(DEBUG) << "PRPPreload.DiskCacheFile::StoreInfoAsync backend not ready";
    SetDelayedStoreTask(base::BindOnce(&DiskCacheFile::DoStoreInfo, weak_factory_.GetWeakPtr(), entry_content));
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(FROM_HERE,
        base::BindOnce(&DiskCacheFile::RunStoreTask, weak_factory_.GetWeakPtr(), true),
        base::Milliseconds(WAIT_INITED_TIME_OUT));
    return;
  }
  DoStoreInfo(entry_content);
}

void DiskCacheFile::DoStoreInfo(const std::string& entry_content)
{
  entry_ = std::make_unique<DiskCacheEntry>(this, *url_, entry_content);
  entry_->Cache();
}

void DiskCacheFile::LoadInfoAsync() {
  if (!disk_cache_backend_factory_) {
    return;
  }
  if (!disk_cache_backend_factory_->CheckBackendAsync(
      base::BindOnce(&DiskCacheFile::BackendComplete, weak_factory_.GetWeakPtr()))) {
    LOG(DEBUG) << "PRPPreload.DiskCacheFile::LoadInfoAsync backend not ready";
    SetDelayedLoadTask(base::BindOnce(&DiskCacheFile::DoLoadInfo, weak_factory_.GetWeakPtr()));
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(FROM_HERE,
        base::BindOnce(&DiskCacheFile::RunLoadTask, weak_factory_.GetWeakPtr(), true),
        base::Milliseconds(WAIT_INITED_TIME_OUT));
    return;
  }
  if (helper_ != nullptr) {
    return;
  }
  DoLoadInfo();
}

void DiskCacheFile::DoLoadInfo()
{
  helper_ = std::make_unique<DiskCacheReadHelper>(this, *url_, entry_loaded_cb_);
  helper_->LoadCache();
}

void DiskCacheFile::EntryReadComplete() {
    helper_.reset();
}

void DiskCacheFile::EntryWriteComplete(DiskCacheEntry* entry) {
  if (entry != entry_.get()) {
    LOG(DEBUG) << "PRPPreload.DiskCacheFile::EntryWriteComplete not current entry";
    return;
  }

  entry_.reset();
}

void DiskCacheFile::SetDelayedStoreTask(base::OnceCallback<void()> delayed_store_task)
{
  delayed_store_task_ = std::move(delayed_store_task);
}

void DiskCacheFile::SetDelayedLoadTask(base::OnceCallback<void()> delayed_load_task)
{
  delayed_load_task_ = std::move(delayed_load_task);
}

void DiskCacheFile::BackendComplete()
{
  RunLoadTask();
  RunStoreTask();
}

void DiskCacheFile::RunStoreTask(bool clear)
{
  if (clear) {
    delayed_store_task_ = base::OnceCallback<void()>();
    return;
  }
  if (!delayed_store_task_.is_null()) {
    std::move(delayed_store_task_).Run();
  }
}

void DiskCacheFile::RunLoadTask(bool clear)
{
  if (clear) {
    delayed_load_task_ = base::OnceCallback<void()>();
    if (!entry_loaded_cb_.is_null()) {
      entry_loaded_cb_.Run(std::string());
    }
    return;
  }
  if (!delayed_load_task_.is_null()) {
    std::move(delayed_load_task_).Run();
  }
}

}  // namespace ohos_prp_preload